shiro项目介绍

在之前的shiro整合项目之后,更加完善shiro功能,之前的代码不予展示与介绍,想了解的请参考shiro整合项目
项目代码获取:https://github.com/pysasuke/s...

功能新增

  • 用户注册

  • 登录错误次数限制(使用redis作缓存)

  • shiro注解配置

  • DTO引入

  • 数据校验(使用hibernate validation)

  • SpringMVC统一异常处理配置

项目结构

java:代码

  • controller:控制层,以下展示注册和登录功能

    @RequestMapping("/login")
    public String login(ShiroUser shiroUser, HttpServletRequest request) {
        Subject subject = SecurityUtils.getSubject();
        UsernamePasswordToken token = new UsernamePasswordToken(shiroUser.getUsername(), shiroUser.getPassword());
        try {
            subject.login(token);//会跳到我们自定义的realm中
            request.getSession().setAttribute("user", shiroUser);
            log.info(shiroUser.getUsername() + "登录");
            return "user/success";
        } catch (UnknownAccountException e) {
            return "user/login";
        } catch (IncorrectCredentialsException e) {
            request.setAttribute("error", "用户名或密码错误");
            return "user/login";
        } catch (ExcessiveAttemptsException e) {
            request.setAttribute("error", "输入密码错误太多次,请稍后再试!");
            return "user/login";
        } catch (Exception e) {
            request.setAttribute("error", "未知错误");
            return "user/login";
        }
    }
    
    @RequestMapping(value = "/register", method = RequestMethod.POST)
    public String register(Model model,
                           @Valid @ModelAttribute ShiroUserDTO shiroUserDTO, BindingResult bindingResult) {
        //数据校验
        if (bindingResult.hasErrors()) {
            List<ObjectError> allErrors = bindingResult.getAllErrors();
            for (ObjectError objectError : allErrors) {
                //输出错误信息
                System.out.println(objectError.getDefaultMessage());
            }
            model.addAttribute("error", "填入信息有误");
            model.addAttribute("user", shiroUserDTO);
            return "/user/register";
        }
        if (shiroUserService.getByUsername(shiroUserDTO.getUsername()) == null) {
            shiroUserService.insertUser(shiroUserDTO);
            return "redirect:/";
        } else {
            model.addAttribute("user", shiroUserDTO);
            model.addAttribute("error", "userName has been registered!");
            return "/user/register";
        }
    }
  • service:业务处理层,以下展示新增用户功能,包含数据转换(DTO到Entity)和密码加密(shiro加密策略)

 public void insertUser(ShiroUserDTO shiroUserDTO) {
        ShiroUser shiroUser = converToAddress(shiroUserDTO);
        shiroUserMapper.insert(shiroUser);
    }
  
 private ShiroUser converToAddress(ShiroUserDTO shiroUserDTO) {
        ShiroUser shiroUser = new ShiroUser();
        BeanUtils.copyProperties(shiroUserDTO, shiroUser);
        passwordEncrypt(shiroUser);
        shiroUser.setCreatetime(new Date());
        shiroUser.setRoleId(1);
        return shiroUser;
    }

  private void passwordEncrypt(ShiroUser shiroUser) {
        String username = shiroUser.getUsername();
        String password = shiroUser.getPassword();
        String salt2 = new SecureRandomNumberGenerator().nextBytes().toHex();
        int hashIterations = 3;
        String algorithmName = "md5";
        SimpleHash hash = new SimpleHash(algorithmName, password,
                username + salt2, hashIterations);
        String encodedPassword = hash.toHex();
        shiroUser.setSalt(salt2);
        shiroUser.setPassword(encodedPassword);
    }
  • dao:数据库交互层

  • entity:实体对象层,以下展示数据校验

@Data
public class ShiroUser {
    private Integer id;
    
    @NotNull(message = "用户名不能为空")
    @Size(min = 3, max = 16, message = "用户名长度必须介于3-16个字符之间")
    
    private String username;
    
    @NotNull(message = "密码不能为空")
    @Size(min = 3, max = 16, message = "{密码长度必须介于3-16个字符之间")
    private String password;
    
    private Date createtime;
    
    private Date lasttime;
    
    @Email(message = "请输入正确的邮箱")
    private String email;
    
    private String sex;
    
    private String salt;
    
    private Integer roleId;
}
  • realm:自定义Realm(shiro相关),以下加入了加密相关代码

public class MyRealm extends AuthorizingRealm {

    @Resource
    private ShiroUserService shiroUserService;

    // 为当前登陆成功的用户授予权限和角色,已经登陆成功了
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(
            PrincipalCollection principals) {
        String username = (String) principals.getPrimaryPrincipal(); //获取用户名
        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        authorizationInfo.setRoles(shiroUserService.getRoles(username));
        authorizationInfo.setStringPermissions(shiroUserService.getPermissions(username));
        return authorizationInfo;
    }

    // 验证当前登录的用户,获取认证信息
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(
            AuthenticationToken token) throws AuthenticationException {
        String username = (String) token.getPrincipal(); // 获取用户名
        ShiroUser shiroUser = shiroUserService.getByUsername(username);
        if (shiroUser != null) {
            SimpleAuthenticationInfo authcInfo = new SimpleAuthenticationInfo(shiroUser.getUsername(), shiroUser.getPassword(), "myRealm");
            //通过SimpleAuthenticationInfo的credentialsSalt设置盐,HashedCredentialsMatcher会自动识别这个盐。
            authcInfo.setCredentialsSalt(ByteSource.Util.bytes(shiroUser.getUsername() + shiroUser.getSalt()));
            return authcInfo;
        } else {
            return null;
        }
    }
}
  • constants:常量类包

  • dto:DTO对象包,以下展示ShiroUserDTO(只包含交互相关的字段)

@Data
public class ShiroUserDTO {

    @NotNull(message = "用户名不能为空")
    @Size(min = 3, max = 16, message = "用户名长度必须介于3-16个字符之间")
    private String username;

    @NotNull(message = "密码不能为空")
    @Size(min = 3, max = 16, message = "{密码长度必须介于3-16个字符之间")
    private String password;

    @Email(message = "请输入正确的邮箱")
    private String email;

    @NotNull(message = "请选择性别")
    private String sex;
}
  • credentials:处理重试次数类包

public class RetryLimitHashedCredentialsMatcher extends HashedCredentialsMatcher {

    @Autowired
    private RedisCache redisCache;


    //匹配用户输入的token的凭证(未加密)与系统提供的凭证(已加密)
    @Override
    public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
        String username = (String) token.getPrincipal();
        //retry count + 1
        //AtomicInteger是一个提供原子操作的Integer类,通过线程安全的方式操作加减。
        AtomicInteger retryCount = redisCache.getCache(Constants.USER + username, AtomicInteger.class);
        if (retryCount == null) {
            retryCount = new AtomicInteger(0);
        }
        //增长
        if (retryCount.incrementAndGet() > 5) {
            //if retry count > 5 throw
            throw new ExcessiveAttemptsException();
        }
        redisCache.putCacheWithExpireTime(Constants.USER + username, retryCount, 600);
        boolean matches = super.doCredentialsMatch(token, info);
        if (matches) {
            //clear retry count
            redisCache.deleteCache(Constants.USER + username);
        }
        return matches;
    }
}

resources:配置文件

  • application.xml:spring配置文件入口,加载spring-config.xml

  • spring-mvc.xml:springmvc配置相关文件

  • spring-config.xml:加载其他集成的配置文件,这里加载spring-mybatis.xml、spring-shiro.xml和db.properties

  • spring-mybatis.xml:mybatis相关配置文件

  • spring-shiro.xml:shiro配置相关文件

    <!-- 凭证匹配器 -->
    <bean id="credentialsMatcher" class="com.py.credentials.RetryLimitHashedCredentialsMatcher">
        <!--指定散列算法为md5,需要和生成密码时的一样-->
        <property name="hashAlgorithmName" value="md5"/>
        <!--散列迭代次数,需要和生成密码时的一样-->
        <property name="hashIterations" value="3"/>
        <!--表示是否存储散列后的密码为16进制,需要和生成密码时的一样,默认是base64-->
        <property name="storedCredentialsHexEncoded" value="true"/>
    </bean>

    <!-- 自定义Realm -->
    <bean id="myRealm" class="com.py.realm.MyRealm">
        <property name="credentialsMatcher" ref="credentialsMatcher"/>
    </bean>
    
     <!--Spring MVC统一异常处理(主要处理shiro注解(如@RequiresPermissions)引发的异常)-->
    <bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
        <property name="exceptionMappings">
            <props>
                <!--未登录-->
                <prop key="org.apache.shiro.authz.UnauthenticatedException">
                    redirect:/user/login
                </prop>
                <!--未授权-->
                <prop key="org.apache.shiro.authz.UnauthorizedException">
                    redirect:/user/login
                </prop>
            </props>
        </property>
        <!--默认跳转页面-->
        <property name="defaultErrorView" value="unauthorized"/>
    </bean>
  • db.properties:数据库相关参数配置

  • log4j.properties:日志相关参数配置

  • mapping:存放mybatis映射文件,以UserMapper.xml为例

  • redis.properties:redis相关参数配置

#redis config
redis.pool.maxTotal=100
redis.pool.maxIdle=10
redis.pool.maxWaitMillis=5000
redis.pool.testOnBorrow=true
redis.pool.maxActive= 100
redis.pool.maxWait= 3000


#redis ip和端口号
redis.ip=127.0.0.1
redis.port=6379
redis.pass=
  • spring-redis.xml:redis相关配置

 <!-- Redis 配置 -->
    <bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
        <property name="maxTotal" value="${redis.pool.maxTotal}"/>
        <property name="maxIdle" value="${redis.pool.maxIdle}"/>
        <property name="maxWaitMillis" value="${redis.pool.maxWaitMillis}"/>
        <property name="testOnBorrow" value="${redis.pool.testOnBorrow}"/>
    </bean>

    <!-- redis单节点数据库连接配置 -->
    <bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
        <property name="hostName" value="${redis.ip}"/>
        <property name="port" value="${redis.port}"/>
        <property name="password" value="${redis.pass}"/>
        <property name="poolConfig" ref="jedisPoolConfig"/>
    </bean>

    <!-- redisTemplate配置,redisTemplate是对Jedis的对redis操作的扩展,有更多的操作,封装使操作更便捷 -->
    <bean id="redisTemplate" class="org.springframework.data.redis.core.StringRedisTemplate">
        <property name="connectionFactory" ref="jedisConnectionFactory"/>
    </bean>
  • spring-mvc-shiro.xml:shiro注解相关配置

 <!-- 开启Shiro注解 -->
    <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"
          depends-on="lifecycleBeanPostProcessor"/>
    <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
        <property name="securityManager" ref="securityManager"/>
    </bean>

webapp:web相关

  • web.xml

 <!-- shiro过滤器定义 -->
    <filter>
        <filter-name>shiroFilter</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
        <init-param>
            <!-- 该值缺省为false,表示生命周期由SpringApplicationContext管理,设置为true则表示由ServletContainer管理 -->
            <param-name>targetFilterLifecycle</param-name>
            <param-value>true</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>shiroFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

 其他文件

logs:日志存放

deploy:部署文件(sql)

  • update.sql

SET FOREIGN_KEY_CHECKS=0;

-- ----------------------------
-- Table structure for `t_permission`
-- ----------------------------
DROP TABLE IF EXISTS `t_permission`;
CREATE TABLE `t_permission` (
  `id` int(11) NOT NULL,
  `role_id` int(11) NOT NULL,
  `permissionname` varchar(100) COLLATE utf8mb4_bin NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;

-- ----------------------------
-- Records of t_permission
-- ----------------------------
INSERT INTO `t_permission` VALUES ('1', '1', 'user:create');
INSERT INTO `t_permission` VALUES ('2', '2', 'user:update');
INSERT INTO `t_permission` VALUES ('3', '1', 'user:update');

-- ----------------------------
-- Table structure for `t_role`
-- ----------------------------
DROP TABLE IF EXISTS `t_role`;
CREATE TABLE `t_role` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `rolename` varchar(20) COLLATE utf8mb4_bin NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;

-- ----------------------------
-- Records of t_role
-- ----------------------------
INSERT INTO `t_role` VALUES ('1', 'teacher');
INSERT INTO `t_role` VALUES ('2', 'student');

-- ----------------------------
-- Table structure for `t_user`
-- ----------------------------
DROP TABLE IF EXISTS `t_user`;
CREATE TABLE `t_user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `userName` varchar(20) NOT NULL,
  `password` varchar(50) NOT NULL,
  `createTime` date DEFAULT NULL,
  `lastTime` datetime DEFAULT NULL,
  `email` varchar(256) DEFAULT NULL,
  `sex` enum('male','female') DEFAULT 'male',
  `salt` varchar(50) DEFAULT NULL,
  `role_id` int(11) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=28 DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of t_user
-- ----------------------------
INSERT INTO `t_user` VALUES ('1', 'admin', '86c4604b628d4e91f5f2a2fed3f88430', '2017-08-28', null, '404158848@qq.com', 'male', '26753209835f4c837066d1cc7d9b46aa', '1');
INSERT INTO `t_user` VALUES ('2', 'test', 'a038892c7b638aad0357adb52cabfb29', '2017-08-28', null, '404158848@qq.com', 'male', '6ced07d939407fb0449d92d9f17cfcd1', '2');
INSERT INTO `t_user` VALUES ('3', 'test1', '4be958cccb89213221888f9ffca6969b', '2017-08-28', null, '404158848@qq.com', 'male', 'c95a278e52daf5166b1ffd6436cde7b7', '1');

pom.xml:maven相关

        <!-- redis begin-->
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>2.8.0</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-redis</artifactId>
            <version>1.6.4.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>com.dyuproject.protostuff</groupId>
            <artifactId>protostuff-core</artifactId>
            <version>1.0.8</version>
        </dependency>

        <dependency>
            <groupId>com.dyuproject.protostuff</groupId>
            <artifactId>protostuff-runtime</artifactId>
            <version>1.0.8</version>
        </dependency>
        <!-- redis end-->

        <!--hibernate validation begin-->
        <!-- spring 数据校验-->
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-validator</artifactId>
            <version>5.0.2.Final</version>
        </dependency>
        <!--hibernate validation end-->

pysasuke
199 声望4 粉丝

青铜分段掌控雷电